#include "crashtrace.h"

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <ucontext.h>
#include <errno.h>
#include <time.h>
#include <string.h>


static Elf32_Sym	*s_sym = NULL;
static char		*s_strtable = NULL;
static int		s_symsize = 0;
static int		s_strsize = 0;
#define			UNKNOWN_SIGNAL	(unsigned int)"Unknown signal"

#define			LOG_FILENAME_LEN		(int)128
static char		s_log_filename[LOG_FILENAME_LEN] = { 0 };

#define			MODULE_NAME_LEN		(int)256
static char		s_module_name[256] = { 0 };

#define			MAX_SIGALNO			(int)100
static unsigned int	s_sigdesc[MAX_SIGALNO];	/* signal describe,  */

#define			SHELL_BUFFER_LEN	(int)512
static char		s_shell_buffer[SHELL_BUFFER_LEN];

#if 0
#define DEBUG_PRINT(str, args...) \
	printf(str, ##args);
#else
	#define DEBUG_PRINT(str, args...)
#endif


/*write back trace log */
static int write_log(const char* log, int log_len)
{
	if (NULL == log || log_len <= 0)
	{
		DEBUG_PRINT("invalid params. log:%d log_len: %d\n", (int)log, log_len);
		return -1;
	}

	FILE* file = fopen(s_log_filename, "r+b");
	if (NULL == file)
	{
		file = fopen(s_log_filename, "w+b");
		if (NULL == file)
		{
			DEBUG_PRINT("log file not exist and create log file failed for %s\n", strerror(errno));
			return -2;
		}
	}

	fseek(file, 0, SEEK_END);
	int len = fwrite(log, log_len, 1, file);
	if (0 == len)
	{
		DEBUG_PRINT("write to log file failed\n");
		return -3;
	}

	fclose(file);

	return 0;
}

static Elf32_Sym* get_symbol(Elf32_Sym* symtable, int tabsize, int addr)
{
	for (; tabsize > 0; tabsize -= sizeof(Elf32_Sym), symtable++)
		if (symtable->st_value == addr)
			return symtable;

	return NULL;		
}

static Elf32_Shdr* read_elf_section(FILE* file, Elf32_Ehdr* ehdr)
{
	Elf32_Shdr* pshdr;	
	int e_size = ehdr->e_shentsize * ehdr->e_shnum;
	
	pshdr = (Elf32_Shdr*)malloc(e_size);
	if (pshdr == NULL) 
	{
		DEBUG_PRINT("malloc shdr error\r\n");
		return NULL;
	}	

	if (fseek (file, ehdr->e_shoff, SEEK_SET))
	{
		DEBUG_PRINT("fseek error\r\n");
		return NULL;
	}	
	
	if (fread(pshdr, e_size, 1, file) != 1)
	{
		DEBUG_PRINT("read file error in func %s\r\n", __func__);
		return NULL;
	}

	return pshdr;
}

static void* read_symtable(FILE* file, Elf32_Shdr* shdr, Elf32_Ehdr* ehdr, int* size)
{
	int sec_num;
	int tb_size;
	void* sym;

	for (sec_num = 0; sec_num < ehdr->e_shnum; sec_num ++, shdr ++)
	{
		if (shdr->sh_type == SHT_SYMTAB)
			break;
	}	
	
	if (sec_num == ehdr->e_shnum)
	{
		DEBUG_PRINT("No symbol table\n");
		return NULL;
	}

	*size = tb_size = shdr->sh_size;
	
	sym = (void*)malloc(tb_size);
	if (sym == NULL)
	{	
		DEBUG_PRINT("malloc error in func %s\r\n", __func__);
		return NULL;
	}

	if (fseek(file, shdr->sh_offset, SEEK_SET))
	{
		DEBUG_PRINT("fseek error\r\n");
		return NULL;
	}	

	if (fread(sym, tb_size, 1, file) != 1)
	{
		DEBUG_PRINT("read file error in func %s\r\n", __func__);
		return NULL;
	}	
	return sym;
}

static void* read_strtable(FILE* file, Elf32_Shdr* shdr, Elf32_Ehdr* ehdr, int* size)
{
	Elf32_Shdr* lshdr = shdr;

	int sec_num;		
	for (sec_num = 0; sec_num < ehdr->e_shnum; sec_num ++, shdr ++)
	{
		if (shdr->sh_type == SHT_SYMTAB)
			break;
	}	

#define SECTION_HEADER_INDEX(index) \
	((index) < SHN_LORESERVE ? (index) : ((index) <= SHN_HIRESERVE ? 0 : (index) - (SHN_HIRESERVE + 1 - SHN_LORESERVE)))

	shdr = lshdr + SECTION_HEADER_INDEX(shdr->sh_link);
#undef SECTION_HEADER_INDEX

	int tb_size;
	*size = tb_size = shdr->sh_size;
	
	void* sym = (void*)malloc(tb_size);
	if (sym ==NULL)
	{	
		DEBUG_PRINT("malloc error in func %s\r\n", __func__);
		return NULL;
	}
	if (fseek (file, shdr->sh_offset, SEEK_SET))
	{
		DEBUG_PRINT("fseek error\r\n");
		return NULL;
	}	

	if (fread(sym, tb_size, 1, file) != 1)
	{
		DEBUG_PRINT("read file error in func %s\r\n", __func__);
		return NULL;
	}	

	return sym;
}

static int read_elf_head(FILE* file, Elf32_Ehdr* ehdr)
{
	if (fread(ehdr, sizeof(Elf32_Ehdr), 1, file) != 1)
	{
		return -1;
	}
	
	return 0;
}


/* get the function'symbol that the "addr" is in the function or the same of the function */
static Elf32_Sym* get_func_sym_by_addr(Elf32_Sym* symtable, int tabsize, int addr)
{
	for (; tabsize > 0; tabsize -= sizeof(Elf32_Sym), symtable ++)
	{
		if (ELF32_ST_TYPE(symtable->st_info) != STT_FUNC || symtable->st_shndx == 0)	/* we only find the function symbol and not a und */
			continue;
		if (!symtable->st_value)
			continue;
		if (addr >= symtable->st_value && addr < (symtable->st_size + symtable->st_value))
			return symtable;
	}
	return NULL;
}

static int printf_symbol_name(int pc)
{
	Elf32_Sym* syml = get_func_sym_by_addr(s_sym, s_symsize, pc);
	if (syml)
	{
		if (syml->st_name >= s_strsize)
		{
			return 1;
		}
		static int step = 1;
		char buf[1024] = { 0 };
		if (1 == step)
		{
			strcat(buf, "\n--------symbol_name--------\n");
			write_log(buf, strlen(buf));
		}

		sprintf(buf, "addr:0x%x st_name:%s\r\n", pc, (s_strtable + syml->st_name));
		write_log(buf, strlen(buf));
		step++;

		if (!strcmp((s_strtable + syml->st_name), "main"))
			return -1;

		return syml->st_value;
	}
	return 0;
}



/* parse cpu context information is architecture depend,
encapsulate as helper function can be easy */
static int parse_cpu_context(void* context)
{
	if (NULL == context)
	{
		DEBUG_PRINT("process context is NULL\n");
		return -1;
	}

	struct ucontext* ct = (struct ucontext*)context;
	int* regs = (int*)ct->uc_mcontext.gregs;
	int epc = regs[14];
	int* presp = (int*)regs[6];

	char buf[1024] = { 0 };
	char tmp[256] = { 0 };

	strcat(buf, "\n--------exception instruction address--------\n");
	
	sprintf(tmp, "exception address: 0x%x\n", epc);
	strcat(buf, tmp);
/*
	strcat(buf, "\n--------CPU registers value--------\n");

	sprintf(tmp, "GS:0x%x\t FS:0x%x\t ES:0x%x\t DS:0x%x\n", regs[0], regs[1], regs[2], regs[3]);
	strcat(buf, tmp);

	sprintf(tmp, "CS:EIP:   0x%x:0x%x\nSS:UESP:  0x%x:0x%x\n", regs[15], regs[14], regs[18], regs[17]);
	strcat(buf, tmp);

	sprintf(tmp, "EDI:0x%x\t ESI:0x%x\t EBP:0x%x\n", regs[4], regs[5], regs[6]);
	strcat(buf, tmp);

	sprintf(tmp, "ESP:0x%x\t EBX:0x%x\t EDX:0x%x\n", regs[7], regs[8], regs[9]);
	strcat(buf, tmp);

	sprintf(tmp, "ECX:0x%x\t\t EAX:0x%x\n", regs[10], regs[11]);
	strcat(buf, tmp);

	sprintf(tmp, "TRAPNO:0x%x\t ERR:0x%x\t EFL:0x%x\n", regs[12], regs[13], regs[16]);
	strcat(buf, tmp);
*/
	write_log(buf, strlen(buf));

	/* ebp will record frame-pointer */
	printf_symbol_name(epc);	/*fault addr*/

	char shell_buf[256] = { 0 };
	sprintf(shell_buf, "%x ", epc);
	strcat(s_shell_buffer, shell_buf);
	while (1)
	{
		int ret = printf_symbol_name(*(presp + 1));
		if (ret == -1 || !ret)
		{
			if (!ret)
				DEBUG_PRINT("programs's user stack error !\n");
			break;
		}

		memset(shell_buf, 0, 255);
		sprintf(shell_buf, "%x ", *(presp + 1));
		strcat(s_shell_buffer, shell_buf);

		presp = (int*)(*(presp));	/* pre function's base sp */
	}

	return 0;
}


static void write_crash_reason(int signo, siginfo_t* siginfo, void* context)
{
	char buf[1024] = { 0 };
	char tmp[256] = { 0 };

	strcat(buf, "\n--------crash reason--------\n");

	if (SIGHUP <= signo || SIGPWR >= signo)
	{
		sprintf(tmp, "Catch fault!  signal is %s , pid = %d\n", s_sigdesc[signo], getpid());
	}
	else
	{
		sprintf(tmp, "Unknown signal, signo=%d,  pid = %d\n", signo, getpid());
	}
	strcat(buf, tmp);

	if (siginfo->si_code == SI_KERNEL)
	{
		sprintf(tmp, "signal is send by Kernel\n");
		strcat(buf, tmp);
	}

	sprintf(tmp, "caused by:\t");
	strcat(buf, tmp);

	if (siginfo->si_code == SEGV_MAPERR)
	{
		sprintf(tmp, "access a fault address: %p\n", siginfo->si_addr);
	}
	else if (siginfo->si_code == SEGV_ACCERR)
	{
		sprintf(tmp, "access a no permission address: %p\n", siginfo->si_addr);
	}
	else
	{
		sprintf(tmp, "Unknown reason\n");
	}
	strcat(buf, tmp);

	write_log(buf, strlen(buf));
}

static void write_crash_backtrace(int signo)
{
	char buf[1024] = { 0 };

	int nnnn = UNKNOWN_SIGNAL;
	if (s_sigdesc[signo] == UNKNOWN_SIGNAL)
	{
		strcat(buf, "Unknown sigal.cannot catch backtrace.");
		write_log(buf, strlen(buf));
		return;
	}
	char shell_cmd[1024] = { 0 };
	sprintf(shell_cmd, "%s%s%s%s", "addr2line ", s_shell_buffer, "-s -C -f -e ", s_module_name);

	FILE* file;
	if ((file = popen(shell_cmd, "r")) == NULL)
	{
		DEBUG_PRINT("the command %s not exist\n", shell_cmd);
		return;
	}
	
	strcat(buf, "\n--------crash backtrace--------\n");

	char backtrace_log[256] = { 0 };
	while (fgets(backtrace_log, sizeof(backtrace_log) - 1, file) != NULL)
	{
		strcat(buf, backtrace_log);
	}

	write_log(buf, strlen(buf));

	pclose(file);
}

static void sigaction_proc(int signo, siginfo_t* info, void* context)
{
	parse_cpu_context(context);

	write_crash_reason(signo, info, context);

	write_crash_backtrace(signo);

	if (s_strtable != NULL)
	{
		free(s_strtable);
	}

	if (s_sym != NULL)
	{
		free(s_sym);
		s_sym = NULL;
	}

	/* sigment fault is critical fault, we should exit right now */
	exit(1);
}

static void exit_proc(int signo, siginfo_t* info, void* context)
{
	char buf[256] = { 0 };
	sprintf(buf, "Program caught %d, will unload dsp\n", signo);
	write_log(buf, strlen(buf));	/* It's not good, may cause thread blocking. */

	DEBUG_PRINT("program exits\n");
	exit(1);
}


static void init_signals_desc()
{
	int i = 0;
	for (i = 0; i < MAX_SIGALNO; i++)
	{
		s_sigdesc[i] = UNKNOWN_SIGNAL;
	}

	s_sigdesc[SIGHUP] = (unsigned int)"SIGHUP";
	s_sigdesc[SIGINT] = (unsigned int)"SIGINT";
	s_sigdesc[SIGQUIT] = (unsigned int)"SIGQUIT";
	s_sigdesc[SIGILL] = (unsigned int)"SIGILL";
	s_sigdesc[SIGTRAP] = (unsigned int)"SIGTRAP";
	s_sigdesc[SIGABRT] = (unsigned int)"SIGABRT";
	s_sigdesc[SIGBUS] = (unsigned int)"SIGBUS";
	s_sigdesc[SIGFPE] = (unsigned int)"SIGFPE";
	s_sigdesc[SIGKILL] = (unsigned int)"SIGKILL";
	s_sigdesc[SIGUSR1] = (unsigned int)"SIGUSR1";

	s_sigdesc[SIGSEGV] = (unsigned int)"SIGSEGV";
	s_sigdesc[SIGUSR2] = (unsigned int)"SIGUSR2";
	s_sigdesc[SIGPIPE] = (unsigned int)"SIGPIPE";
	s_sigdesc[SIGALRM] = (unsigned int)"SIGALRM";
	s_sigdesc[SIGTERM] = (unsigned int)"SIGTERM";
	s_sigdesc[SIGSTKFLT] = (unsigned int)"SIGSTKFLT";
	s_sigdesc[SIGCHLD] = (unsigned int)"SIGCHLD";
	s_sigdesc[SIGCONT] = (unsigned int)"SIGCONT";
	s_sigdesc[SIGSTOP] = (unsigned int)"SIGSTOP";
	s_sigdesc[SIGTSTP] = (unsigned int)"SIGTSTP";

	s_sigdesc[SIGTTIN] = (unsigned int)"SIGTTIN";
	s_sigdesc[SIGTTOU] = (unsigned int)"SIGTTOU";
	s_sigdesc[SIGURG] = (unsigned int)"SIGURG";
	s_sigdesc[SIGXCPU] = (unsigned int)"SIGXCPU";
	s_sigdesc[SIGXFSZ] = (unsigned int)"SIGXFSZ";
	s_sigdesc[SIGVTALRM] = (unsigned int)"SIGVTALRM";
	s_sigdesc[SIGPROF] = (unsigned int)"SIGPROF";
	s_sigdesc[SIGWINCH] = (unsigned int)"SIGWINCH";
	s_sigdesc[SIGIO] = (unsigned int)"SIGIO";
	s_sigdesc[SIGPWR] = (unsigned int)"SIGPWR";
}

/* Register signals to programe deal with */
static void reg_signals_to_prg_deal(int signo)
{
	struct sigaction sigact;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = SA_ONESHOT | SA_SIGINFO;
	sigact.sa_sigaction = sigaction_proc;
	sigaction(signo, &sigact, NULL);
}

/* Register signals to system deal with */
static void reg_sig_to_sys_deal(int signo)
{
	struct sigaction sigact;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = 0;
	sigact.sa_handler = SIG_DFL;
	sigaction(signo, &sigact, NULL);
}


/* Register the signal to exit */
static void reg_sig_to_exit(int signo)
{
	struct sigaction sigact;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = SA_ONESHOT | SA_SIGINFO;
	sigact.sa_sigaction = exit_proc;
	sigaction(signo, &sigact, NULL);
}

/* Register the signal to ignore */
static void reg_sig_to_ingore(int signo)
{
	struct sigaction sigact;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = 0;
	sigact.sa_handler = SIG_IGN;
	sigaction(signo, &sigact, NULL);
}

int regist_exception(char* module_name)
{
	init_signals_desc();

	/* Set signals capture */
	reg_signals_to_prg_deal(SIGSEGV);	/* Invalid memory reference. */
	reg_signals_to_prg_deal(SIGILL);	/* Illegal instruction. */
	reg_signals_to_prg_deal(SIGABRT);	/* Process abort signal */
	reg_signals_to_prg_deal(SIGBUS);	/* Access to an undefined portion of a memory object. */
	reg_signals_to_prg_deal(SIGFPE);	/* Erroneous arithmetic operation. */
	reg_signals_to_prg_deal(SIGSYS);	/* Bad system call. */

	reg_sig_to_sys_deal(SIGINT);		/* Terminal interrupt signal.(ctrl+c) */
	reg_sig_to_sys_deal(SIGQUIT);		/* Terminal quit signal. */
	reg_sig_to_sys_deal(SIGTRAP);		/* Trace/breakpoint trap. */
	reg_sig_to_sys_deal(SIGPWR);		/* Power failure */
	reg_sig_to_sys_deal(SIGTERM);		/* Termination signal. */
	reg_sig_to_ingore(SIGHUP);			/* Hangup. */

	FILE* file;
	/* read elf file.(compile with -g) */
	file = fopen(module_name, "rb");
	if (file == NULL)
	{
		DEBUG_PRINT("open file %s error\r\n", module_name);
		return errno;
	}

	Elf32_Ehdr ehdr;
	/* read the elf file's head */
	if (read_elf_head(file, &ehdr) == -1)
		return errno;

	Elf32_Shdr* shdr;
	/* read all sections of the elf file */
	shdr = read_elf_section(file, &ehdr);
	if (shdr == NULL)
		return errno;

	/* read static symbol table through symbol section*/
	s_sym = (Elf32_Sym*)read_symtable(file, shdr, &ehdr, &s_symsize);
	if (s_sym == NULL)
		return errno;

	/* read string table through string section */
	s_strtable = (char*)read_strtable(file, shdr, &ehdr, &s_strsize);
	if (s_strtable == NULL)
		return errno;

	free(shdr);

	return 0;
}

int get_module_name(char* module_name, int len)
{
	char buf[MODULE_NAME_LEN] = { 0 };
	char* tmp = buf;
	int tmp_len = 0;

	pid_t pid = getpid();
	char path[100] = { 0 };
	sprintf(path, "/proc/%d/exe", pid);
	int count = 0;
	count = readlink(path, buf, MODULE_NAME_LEN);
	if (count < 0 || count >= MODULE_NAME_LEN)
	{
		DEBUG_PRINT("Current System Not Surport Proc.\n");
		return -1;
	}
	else
	{
		int index = 0;
		index = count - 1;

		for (; index >= 0; index--) /* select process name */
		{
			if (buf[index] == '/')
			{
				index++;
				tmp += index;
				break;
			}
		}
	}
	tmp_len = strlen(tmp);
	if (0 == tmp_len)
	{
		DEBUG_PRINT("Get task fail./n");
		return -1;
	}

	if (len <= tmp_len + 1)
	{
		DEBUG_PRINT("len(%d) is less than taskname(%s)'s len.\n", len, tmp);
		return -1;
	}

	strcpy(module_name, tmp);

	return 0;
}

void init_crashtrace(char* log_filename)
{
	get_module_name(s_module_name, MODULE_NAME_LEN);

	if (NULL != log_filename)
	{
		strcpy(s_log_filename, log_filename);
	}
	else
	{
		time_t t;
		t = time(NULL);
		struct tm* local_time = localtime(&t);

		sprintf(s_log_filename, "%s_%d-%02d-%02d_%02d-%02d-%02d%s", s_module_name,
			local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday,
			local_time->tm_hour, local_time->tm_min, local_time->tm_sec, ".crash");
	}

	regist_exception(s_module_name);
}



